package br.mia.unifor.onaga.cloud.execute;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.net.telnet.TelnetClient;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.ComputeServiceContextFactory;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;

import br.mia.unifor.onaga.cloud.artifact.Appliance;
import br.mia.unifor.onaga.cloud.artifact.Application;
import br.mia.unifor.onaga.cloud.artifact.Instance;
import br.mia.unifor.onaga.cloud.artifact.Provider;
import br.mia.unifor.onaga.cloud.artifact.script.ScriptParser;
import br.mia.unifor.onaga.cloud.artifact.script.Scriptlet;
import br.mia.unifor.onaga.util.SSHClient;

public abstract class ComputeProvider {
	protected ComputeServiceContext context = null;
	protected Logger logger = Logger.getLogger(getClass().getName());

	public ComputeProvider(Provider provider) throws IOException {
		Properties properties = new Properties();
		
		InputStream input = Main.class.getClass()
				.getResourceAsStream(provider.getCredentialPath());
		if(input == null){			
			input = new FileInputStream(provider.getCredentialPath());
		}
		
		properties.load(input);
		this.context = new ComputeServiceContextFactory().createContext(
				provider.getName(), properties.getProperty("accessKey"),
				properties.getProperty("secretKey"));
		
		Set set = this.context.getComputeService().listNodes();
		logger.info("todas as suas instâncias "+set.toString());

	}

	public ComputeServiceContext getContext() {
		return context;
	}

	public Set<? extends Image> listImages() {
		return (Set<? extends Image>) context.getComputeService().listImages();
	}

	public void stopInstance(Instance instance) {
		NodeMetadata metadata = context.getComputeService().getNodeMetadata(
				instance.getId());

		String ip = metadata.getPublicAddresses().iterator().next();

		runScript(instance, instance.getOnShutdown(), ip, logger);
		stopInstanceAction(instance);
	}

	protected void stopInstanceAction(Instance instance) {
		context.getComputeService().suspendNode(instance.getId());
	}

	public boolean validateSSHConnection(String ip) {
		TelnetClient tc = new TelnetClient();
		try {
			tc.connect(ip, 22);
			tc.disconnect();
			return true;
		} catch (SocketException e) {
			logger.log(Level.SEVERE, "erro ao tentar conexao"
					+ NodeState.RUNNING, e);
		} catch (IOException e) {
			logger.log(Level.SEVERE, "erro ao tentar conexao"
					+ NodeState.RUNNING, e);
		}

		return false;
	}

	public void startInstance(Instance instance, Appliance appliance) {
		
		logger.info("a instância será iniciada "+instance.getId());

		NodeMetadata metadata = context.getComputeService().getNodeMetadata(
				instance.getId());
		
		logger.info("o estado da "+instance.getId()+" ("+metadata.getState()+")");
		if (!NodeState.RUNNING.equals(metadata.getState())) {
			changeInstanceType(instance, metadata);

			startInstanceAction(instance);
			metadata = context.getComputeService().getNodeMetadata(
					instance.getId());

			String ip = metadata.getPublicAddresses().iterator().next();

			if (!validateSSHConnection(ip)) {
				stopInstance(instance);
				startInstance(instance, appliance);
			} else {

				instance.setPublicIpAddress(metadata.getPublicAddresses()
						.iterator().next());

				instance.setPrivateIpAddress(metadata.getPrivateAddresses()
						.iterator().next());

				List<Scriptlet> scriptlets = new ArrayList<Scriptlet>(instance
						.getOnStartup().size());

				for (Application app : instance.getApplications()) {

					for (Scriptlet scriptlet : app.getConfig()) {
						scriptlets.add(ScriptParser.parse(appliance, scriptlet,
								instance));
					}

					for (Scriptlet scriptlet : app.getOnStartup()) {
						scriptlets.add(ScriptParser.parse(appliance, scriptlet,
								instance));
					}
				}

				for (Scriptlet scriptlet : instance.getOnStartup()) {
					scriptlets.add(ScriptParser.parse(appliance, scriptlet,
							instance));
				}

				instance.setOnStartup(scriptlets);

				runScript(instance, instance.getOnStartup(), ip, logger);
			}

		} else {
			if (!metadata.getHardware().getId()
					.equals(instance.getType().getProviderProfile())) {
				stopInstance(instance);
				
				changeInstanceType(instance, metadata);

				startInstance(instance, appliance);
			} else {
				instance.setPublicIpAddress(metadata.getPublicAddresses()
						.iterator().next());

				instance.setPrivateIpAddress(metadata.getPrivateAddresses()
						.iterator().next());
				
				List<Scriptlet> scriptlets = new ArrayList<Scriptlet>();
				
				for (Application app : instance.getApplications()) {
					if(app.getConfig() != null)
						for (Scriptlet scriptlet : app.getConfig()) {
							scriptlets.add(ScriptParser.parse(appliance, scriptlet,
									instance));
						}
				}
				runScript(instance, scriptlets, instance.getPublicIpAddress(), logger);
			}
		}
		
		logger.info("a instância FOI iniciada "+instance.getId());
	}

	protected void startInstanceAction(Instance instance) {
		context.getComputeService().resumeNode(instance.getId());
	}

	public static void runScript(Instance instance, List<Scriptlet> script,
			String ip, Logger logger) {
		if (script != null)
			for (Scriptlet scriptlet : script) {
				runScript(instance, scriptlet, ip, logger);
			}
	}

	public static void runScript(Instance instance, Scriptlet script,
			String ip, Logger logger) {
		if (script != null && script.getScripts().size() > 0) {

			logger.info(script.toString());

			if (instance.getType().getProvider().getPrivateKey() != null) {
				SSHClient.exec(script, instance.getType().getProvider()
						.getUserName(), ip, instance.getType().getProvider()
						.getPrivateKey(), Boolean.FALSE);
			} else {
				SSHClient.exec(script, instance.getType().getProvider()
						.getUserName(), ip, instance.getType().getProvider()
						.getPassword(), Boolean.TRUE);
			}
		}
	}

	public boolean changeInstanceType(Instance instance, NodeMetadata metadata) {
		
		logger.info("a instância "+instance.getId()+" mudara de recurso INI");

		if (!metadata.getHardware().getId()
				.equals(instance.getType().getProviderProfile())) {
			changeInstanceType(instance);
		}
		
		logger.info("a instância "+instance.getId()+" mudara de recurso FIM");

		return true;
	}

	public abstract boolean changeInstanceType(Instance instance);

}
